UUPS 是可升級合約的一種實作方式,它的特色是將 upgrade
函式寫在邏輯合約,合約升級的時候也會一併調整下次升級的權限機制。
代理合約通常需要設計兩個功能:
ERC-1967 只規範儲存邏輯合約的位置,至於如何更換邏輯合約的地址,則交由不同種類的可升級合約去實作。
UUPS 的特色是將升級函式寫在邏輯合約,TransparentProxy 則是將升級函式寫在代理合約。
升級函式位於邏輯合約,升級的時候,是呼叫「代理合約」的 upgrade
,然後再 delegatecall
到邏輯合約的 upgrade
。
UUPS 的好處是,升級時 upgrade
的邏輯也能一起升級,舉例來說,原本由一個 EOA 來掌權升級,升級之後,下次變成需要多把 EOA 才能升級,當然也可能反過來從複雜變簡單的機制。
UUPS 一體兩面的危險是,升級機制沒寫好,可能導致之後無法升級。
UUPS 規定在邏輯合約實作 proxiableUUID
,它會回傳邏輯合約地址的儲存位置,若遵循 ERC-1967,storage slot 的位置就會是 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
,來自 keccak256("eip1967.proxy.implementation") - 1
。
proxiableUUID
是為了確保升級時,新的邏輯合約也有將邏輯合約地址儲存在同一個 storage slot。
以下為 Openzeppelin 的程式碼片段:
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
每個邏輯合約都有的升級函式,OZ 取名 upgradeToAndCall
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
其中,_authorizeUpgrade
是開發者要自行實作的部分,用於設計更換邏輯地址的權限。
--
以下是暫時的實作結果,還沒設計升級權限,這會導致每個人都可以升級合約,是最危險的情況,通常要為 _authorizeUpgrade
加上 onlyOwner
。但我們不能直接引用 Ownable.sol 在邏輯合約,因為我們不能在邏輯合約寫 constructor
,明天再來介紹 Initializable 的概念。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
contract UUPSProxy is ERC1967Proxy {
constructor(address _implementation, bytes memory _data) payable ERC1967Proxy(_implementation, _data) {}
}
contract ImplOne is UUPSUpgradeable {
function _authorizeUpgrade(address newImplementation) internal override {}
function myNumber() public pure returns (uint256) {
return 1;
}
}
contract ImplTwo is UUPSUpgradeable {
function _authorizeUpgrade(address newImplementation) internal override {}
function myNumber() public pure returns (uint256) {
return 2;
}
}